//
// Created by Strawberry on 2022/4/12.
//


#include "MemoryApi.h"
//存储交换空间
std::map<long long int, Page *> g_change_memory;
//存储全部页表
std::map<int, Frame> g_page_table;
//存储全部的帧
std::list<Frame *> g_all_frame;
//存储全部的空闲帧
std::list<Frame *> g_all_free_frame;

void MemoryApi::initial_memory(int page_frame_size,int frame_total_num){

    //初始化页/帧大小和帧数目
    g_frame_size = page_frame_size;
    g_frame_num = frame_total_num;

    //通过页大小计算位数    
    int temp = g_frame_size;
    //先将其转化为2进制
    //将计算得到的frame的起始地址转为二进制再转为字符串
    int a[64], i;
    long long temp2;
    for (i = 0; temp > 0; i++) {
        a[i] = temp % 2;
        temp = temp / 2;
    }
    temp2 = a[i - 1];

    for (i = i - 2; i >= 0; i--) {
        temp2 = (temp2 * 10) + a[i];
    }
    do {
        temp2 = temp2 / 10;
        g_offset_n++;
    } while (temp2 > 0);
    g_offset_n--;

    //根据帧数来循环生成并添加
    for (i = 1; i <= g_frame_num; i++) {
        auto frame = new Frame(nullptr, true, i);
        g_all_frame.push_back(frame);
        g_all_free_frame.push_back(frame);
    }

}

//TESTED
bool MemoryApi::access_memory(PageTable *page_table, long long int logical_loc) {
    return (*page_table).access(logical_loc);
}

int MemoryApi::get_free_frame_num() {
    int num = 0;
    for (const auto &item : g_all_free_frame){
        if(item != nullptr){
            num++;
        }
    }
    return num;
}

void MemoryApi::free_memory(PageTable *page_table) {
    (*page_table).free();
}

void MemoryApi::load_into_memory(PageTable *page_table, long long int logical_loc) {
    if (get_free_frame_num() < 1) {
        //先LRU获得一个空闲帧
        (*page_table).lru();
    }
    if (g_change_memory.find(logical_loc) == g_change_memory.end()) {
        //如果交换空间中没有要调入的帧 说明其不在交换区 直接调入
        //从全部的空闲帧中获取一个帧
        auto frame = g_all_free_frame.front();

        //构造所需页
        auto page = new Page(frame->number, true, logical_loc, 0);
        //把空闲帧指向该页
        frame->page = page;
        frame->is_free = false;
        //加入页表
        page_table->table[page->page_number] = frame;
        g_all_free_frame.erase(g_all_free_frame.begin());
    } else {
        //交换空间有需要调入的帧 则直接从交换空间拿了调入
        page_table->swap_in(logical_loc);
    }


}

bool MemoryApi::is_in_memory(PageTable *page_table, long long int logical_loc) {
    return (*page_table).in_memory(logical_loc);
}

//TESTED
long long MemoryApi::transfer_location(Page *page) {
    return ((page->frame_number << g_offset_n) + (page->logical_loc & ((1 << g_offset_n) - 1)));
}

//TESTED
int MemoryApi::get_page_number(long long int logical_loc) {
    return Page::get_page_number(logical_loc);
}

//TESTED
void MemoryApi::show_all_free_frame() {
    std::cout << "free_frame_number\t\tfree_frame_info" << std::endl;
    for (const auto &item : g_all_free_frame){
        if(item != nullptr){
            std::cout << item->number << "\t\t" << *item << std::endl;
        }
    }
}

//TESTED
void MemoryApi::show_all_frame() {
    std::cout << "frame_number\t\tframe_info" << std::endl;
    for (const auto &item : g_all_frame){
        if(item != nullptr){
            std::cout << item->number << "\t\t" << *item << std::endl;
        }
    }
}

#ifdef UNIT_TEST

#include <gtest/gtest.h>

/**
 * 初始化内存部分 
 * MemoryApi::initial_memory
 */
TEST(MEMORY_TEST, INITIAL_MEMORY_TEST)
{
    EXPECT_EQ(MemoryApi::get_free_frame_num(), g_frame_num);
    //4096的页大小对应12位
    EXPECT_EQ(g_offset_n, 12);
}

/**
 * 虚拟地址与物理地址转换
 * MemoryApi::transfer_location
 */
TEST(MEMORY_TEST, TRANSFER_LOCATION_TEST)
{
    //设定地址为
    long long a = 0b1010000000000001;
    //帧号为7 
    auto page = new Page(7, false, a, 0);
    EXPECT_EQ(MemoryApi::transfer_location(page), 0x7001);
    page->logical_loc = 0xa001;
    EXPECT_EQ(MemoryApi::transfer_location(page), 0x7001);
    page->logical_loc = 40961;
    EXPECT_EQ(MemoryApi::transfer_location(page), 0x7001);

}

/**
 * 根据虚拟地址解析出他所在页表的哪一页
 * MemoryApi::get_page_number
 */
TEST(MEMORY_TEST, GET_PAGE_NUMBER_TEST)
{
    //设定地址为
    long long a = 0b1010000000000001;
    //偏移量为12位 故根据虚拟地址可以看到前4位代表所在页数 即12
    EXPECT_EQ(MemoryApi::get_page_number(a), 10);
}

/**
 * 载入内存、访存、释放内存
 * MemoryApi::access_memory
 * MemoryApi::load_into_memory
 * MemoryApi::is_in_memory
 * MemoryApi::get_free_frame_num
 * MemoryApi::free_memory
 */
TEST(MEMORY_TEST, ACCESS_MEMORY_AND_LOAD_TEST)
{
    //设定地址为
    long long a = 0b1010000000000001;
    PageTable page_table;
    //初次访问时 未load入内存 故应该返回false
    EXPECT_EQ(MemoryApi::access_memory(&page_table, a), false);

    //然后load_into_memory
    MemoryApi::load_into_memory(&page_table, a);
    //已经load 应该返回true 且现在空闲帧已经被用了一个 应该剩63个
    EXPECT_EQ(MemoryApi::access_memory(&page_table, a), true);
    EXPECT_EQ(MemoryApi::get_free_frame_num(), 63);
    //然后再load一个 b
    long long b = 57346;
    MemoryApi::load_into_memory(&page_table, b);
    //然后再访问b 应该为true
    EXPECT_EQ(MemoryApi::access_memory(&page_table, b), true);
    //此时a的access_flag应该递增 为1
    EXPECT_EQ(page_table.table[MemoryApi::get_page_number(a)]->page->access_flag, 1);
    //且空闲帧数目应该为62
    EXPECT_EQ(MemoryApi::get_free_frame_num(), 62);

    //此时a和b俩对应的页应该都在内存中
    EXPECT_EQ(MemoryApi::is_in_memory(&page_table, a), true);
    EXPECT_EQ(MemoryApi::is_in_memory(&page_table, b), true);

    //然后free掉该page_table的全部内存
    MemoryApi::free_memory(&page_table);
    //此时a和b应该不在内存 且空闲帧的数目应该恢复64
    EXPECT_EQ(MemoryApi::get_free_frame_num(), 64);
    EXPECT_EQ(MemoryApi::is_in_memory(&page_table, a), false);
    EXPECT_EQ(MemoryApi::is_in_memory(&page_table, b), false);

    //接下来模拟内存不够时的情况
    //假设一共有两个空闲帧 但是要load三个页时

    //首先重置整个空闲帧列
    g_all_frame.clear();
    g_all_free_frame.clear();
    for (int i = 1; i <= 2; i++)
    {
        auto frame = new Frame(nullptr, true, i);
        g_all_frame.push_back(frame);
        g_all_free_frame.push_back(frame);
    }
    //现在应该有两个空闲帧
    EXPECT_EQ(MemoryApi::get_free_frame_num(), 2);

    //然后load a和b
    PageTable page_table2;
    MemoryApi::load_into_memory(&page_table2, a);

    MemoryApi::load_into_memory(&page_table2, b);
    //然后访问a 此时b的access_flag为1 如果LRU则他被换走
    MemoryApi::access_memory(&page_table2, a);

    EXPECT_EQ(page_table2.table[MemoryApi::get_page_number(a)]->page->access_flag, 0);
    EXPECT_EQ(page_table2.table[MemoryApi::get_page_number(b)]->page->access_flag, 1);

    //然后load c 此时应该把b换走 即b不在内存中了 a c在
    long long c = 0xf002;
    MemoryApi::load_into_memory(&page_table2, c);
    EXPECT_EQ(MemoryApi::is_in_memory(&page_table2, a), true);
    EXPECT_EQ(MemoryApi::is_in_memory(&page_table2, b), false);
    EXPECT_EQ(MemoryApi::is_in_memory(&page_table2, c), true);

    //先访问一次a 则下次load的时候根据LRU 被swap out的是c
    MemoryApi::access_memory(&page_table2, a);
    //此时b应该在交换空间 然后再去load b 时 应该从交换空间swap in    
    MemoryApi::load_into_memory(&page_table2, b);
}


#endif
